Skip to content

Fix runtime AttributeError in wordnet type casts, improve type safety and API contracts - #1354

Merged
bact merged 13 commits into
devfrom
copilot/narrow-type-hints-update
Mar 22, 2026
Merged

Fix runtime AttributeError in wordnet type casts, improve type safety and API contracts#1354
bact merged 13 commits into
devfrom
copilot/narrow-type-hints-update

Conversation

Copilot AI commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

What do these changes do

  • Fix a runtime AttributeError in pythainlp/corpus/wordnet.py caused by accessing wordnet.Synset/wordnet.Lemma on a WordNetCorpusReader instance inside cast() calls.
  • Fix morphy() to correctly pass its pos argument through to the underlying NLTK call (it was silently ignoring the argument).
  • Fix bahttext() to reject None input with a TypeError, clarifying the API contract.
  • Fix the ValueError message in pythainlp/tag/tltk.py to correctly display the unsupported corpus name.
  • Add test cases for morphy() with both pos=None and a non-None pos value.
  • Update the bahttext(None) test to expect a TypeError.

What was wrong

  • wordnet (from from nltk.corpus import wordnet) is a WordNetCorpusReader instance, not a module. Accessing wordnet.Synset or wordnet.Lemma inside cast() calls evaluates the attribute lookup at runtime, raising AttributeError: 'WordNetCorpusReader' object has no attribute 'Synset'.
  • morphy(form, pos=pos) was implemented as morphy(form, pos=None), silently ignoring the caller-supplied part-of-speech and potentially returning wrong base forms.
  • bahttext() accepted Optional[float], implying None was a valid input, but the intended contract is to only accept numeric values.
  • The ValueError in tltk.pos_tag used {0} as a placeholder instead of {corpus!r}, always rendering as "tltk not support 0 corpus.".

How this fixes it

  • Import Synset and Lemma directly from nltk.corpus.reader.wordnet (where they are actual classes) and replace all wordnet.Synset / wordnet.Lemma references with these names.
  • Pass pos through in morphy(): return cast(str, wordnet.morphy(form, pos=pos)).
  • Change bahttext() signature to number: float and add an explicit isinstance guard that raises TypeError for non-numeric input.
  • Fix the f-string in tltk.pos_tag to f"tltk not support {corpus!r} corpus.".
  • Add morphy("dogs", pos=None) and morphy("dogs", pos="n") assertions to testx_corpus.py.
  • Replace assertEqual(bahttext(None), "") with assertRaises(TypeError) in test_util.py.

Your checklist for this pull request

  • Passed code styles and structures
  • Passed code linting checks and unit test

📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

@bact bact added the refactoring a technical improvement which does not add any new features or change existing features. label Mar 21, 2026
@bact bact added this to the 5.3.3 milestone Mar 21, 2026
@bact bact added this to PyThaiNLP Mar 21, 2026
@bact bact moved this to In progress in PyThaiNLP Mar 21, 2026
Copilot AI and others added 6 commits March 21, 2026 23:17
- ancient/currency.py: narrow dict -> dict[str, float]
- augment/word2vec/bpemb_wv.py: use cast instead of type: ignore
- augment/word2vec/core.py: format long line
- augment/wordnet.py: narrow list -> list[Synset], list -> list[list[str]]
- benchmarks/word_tokenization.py: add NDArray import, narrow types,
  fix exception chaining, use dtype=np.int8
- chunk/__init__.py: add blank line after docstring
- chunk/crfchunk.py: add cast, replace type: ignore
- coref/core.py: replace _MODEL global with _MODEL_CACHE dict,
  narrow list[dict] -> list[dict[str, Any]], use cast
- corpus/common.py: format conditions and dict assignment
- corpus/core.py: move Any/Optional to runtime imports, add cast,
  remove double blank lines, drop type: ignore comments
- corpus/wordnet.py: add cast, replace all type: ignore comments,
  fix custom_lemmas to return None explicitly
- el/_multiel.py: narrow list[dict] -> list[dict[str, Any]], use cast
- el/core.py: narrow return type, fix docstring param
- generate/wangchanglm.py: use re.Pattern[str], add cast
- parse/ud_goeswith.py: rename r -> root_adjustments, format expression
- phayathaibert/core.py: narrow Callable types, add cast
- soundex/complete_soundex.py: narrow list/tuple types in signatures
- soundex/sound.py: add cast, replace type: ignore
- spell/phunspell.py: add cast, replace type: ignore
- spell/tltk.py: add cast, replace type: ignore

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- spell/wanchanberta_thai_grammarly.py: add # type: ignore[misc] to BertModel
- spell/words_spelling_correction.py: replace global _WSC with _WSC_CACHE,
  use import_module for dependency checks, improve docstrings, add cast,
  fix numpy dtype precision
- summarize/freq.py: narrow __rank and __compute_frequencies signatures
- summarize/keybert.py: add cast/NDArray, fix embed return type, fix
  _rank_keywords signatures, add exception chaining
- tag/_tag_perceptron.py: dict[str, Union[dict, list]] -> dict[str, Any]
- tag/chunk.py, tag/crfchunk.py: add blank line after module docstring
- tag/thai_nner.py: narrow dict -> dict[str, Any], add Any import
- tag/thainer.py: narrow list -> list[tuple[str,str]],
  dict -> dict[str, Union[str, bool]]
- tag/tltk.py: replace # type: ignore with cast
- tag/wangchanberta_onnx.py: fix mutable default arg, use NDArray types,
  use cast, fix sp.encode -> sp.EncodeAsIds
- tokenize/core.py: add cast for paragraph_tokenize return
- tokenize/han_solo.py: dict[str, list] -> dict[str, list[Any]]
- tokenize/newmm.py: defaultdict -> defaultdict[int, list[int]]
- translate/tokenization_small100.py: multiple cast improvements,
  fix __getstate__/__setstate__ types, add # type: ignore[misc]
- translate/word2word_translate.py: add cast for Optional[list[str]]
- transliterate/ipa.py: add cast for all returns
- transliterate/lookup.py: add cast for bool and str returns
- transliterate/thai2rom.py: add # type: ignore[misc] to nn.Module subclasses
- transliterate/thai2rom_onnx.py: NDArray types, cast, encoding fix

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- transliterate/thaig2p.py: add Any import, NDArray[Any] for generics,
  type: ignore[misc] on nn.Module subclasses
- transliterate/thaig2p_v2.py, umt5_thaig2p.py: replace type: ignore
  with cast for pipeline output
- transliterate/tltk.py: add cast import; use cast(str, ...) for tltk
  return values
- transliterate/w2p.py: cast NDArray[np.float32] assignments, add
  docstrings, refine _sigmoid/_grucell/_gru/_encode signatures
- transliterate/wunsen.py: use cast(str, ...) for thap return
- ulmfit/core.py: update process_thai signature/body to Optional params
  with defaults, NDArray[np.float32] for document_vector, typed
  merge_wgts wgts param
- util/abbreviation.py: cast return type of _replace
- util/collate.py: Iterable[str] parameter, update docstring
- util/keywords.py: Counter[str] return type, update docstring
- util/numtoword.py: Optional[float] for bahttext parameter
- util/pronounce.py: reformat rhyme generator expression
- util/thai.py: reformat MappingProxyType, dict[str,int] return types
- wangchanberta/core.py: cast tokenize return
- word_vector/core.py: NDArray[np.float32] for sentence_vectorizer,
  np.zeros with dtype, cast for model method returns
- wsd/core.py: replace _MODEL global with _MODEL_CACHE dict, rename
  loop vars, typed custom_dict, float() for get_score return

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI changed the title [WIP] Narrow type hints and update docstrings for clarity Narrow type hints safely from PR #1344, fix runtime and mypy errors Mar 21, 2026
Copilot AI requested a review from bact March 21, 2026 23:58
Copilot finished work on behalf of bact March 21, 2026 23:58
bact added 2 commits March 22, 2026 10:05
Refactor frequency computation to use Counter and simplify filtering based on min and max cut values.
Copilot finished work on behalf of bact March 22, 2026 11:08
@coveralls

coveralls commented Mar 22, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 66.447% (-0.05%) from 66.492%
when pulling d1dff74 on copilot/narrow-type-hints-update
into 1b55f4f on dev.

@bact
bact marked this pull request as ready for review March 22, 2026 12:41
@bact
bact requested a review from Copilot March 22, 2026 12:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR focuses on tightening and correcting type hints across PyThaiNLP, replacing broad Any/type: ignore usage with narrower typing (often via cast()), and addressing a runtime typing issue in the WordNet wrapper.

Changes:

  • Fix WordNet typing/runtime usage by importing Synset/Lemma directly and updating annotations/casts accordingly.
  • Replace multiple # type: ignore[...] returns with explicit cast(...) and refine return/parameter types (including numpy.typing.NDArray).
  • Introduce simple global caches for some heavyweight models to reduce repeated initialization.

Reviewed changes

Copilot reviewed 54 out of 54 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pythainlp/wsd/core.py Refines types and introduces device-based model caching for WSD scoring.
pythainlp/word_vector/core.py Improves return typing (casts/NDArray) and enforces float32 vector output.
pythainlp/wangchanberta/core.py Replaces type ignores with casts for tokenizer return types.
pythainlp/util/thai.py Formatting plus more precise return types for Thai text utilities.
pythainlp/util/pronounce.py Formatting-only change to comprehension layout.
pythainlp/util/numtoword.py Broadens typing for bahttext parameter and updates docstring typing.
pythainlp/util/keywords.py Narrows Counter typing and documents None return on empty input.
pythainlp/util/collate.py Narrows Iterable typing to Iterable[str] and updates doc types.
pythainlp/util/abbreviation.py Replaces type ignore with cast for external return type.
pythainlp/ulmfit/core.py Tightens callable/rule types, NDArray return types, and float32 conversions.
pythainlp/transliterate/wunsen.py Replaces type ignore with cast for transliteration return type.
pythainlp/transliterate/w2p.py Refines NDArray annotations and adds casts for loaded numpy arrays.
pythainlp/transliterate/umt5_thaig2p.py Replaces type ignore with cast and safer output extraction.
pythainlp/transliterate/tltk.py Adds casts around tltk outputs and formats processing steps.
pythainlp/transliterate/thaig2p_v2.py Replaces type ignore with cast and safer output extraction.
pythainlp/transliterate/thaig2p.py Narrows NDArray typing to avoid NDArray without element type.
pythainlp/transliterate/thai2rom_onnx.py Adds NDArray typing, encoding for JSON, and dtype fixes/casts for ONNX IO.
pythainlp/transliterate/lookup.py Replaces type ignores with casts for lookup-derived values.
pythainlp/transliterate/ipa.py Replaces type ignores with casts for epitran return types.
pythainlp/translate/word2word_translate.py Replaces type ignore with cast for external translation return.
pythainlp/translate/tokenization_small100.py Adds casts/typed dict state and narrows return types for tokenizer helpers.
pythainlp/tokenize/newmm.py Narrows graph typing for BFS helper.
pythainlp/tokenize/han_solo.py Narrows feature dict typing (list[Any]).
pythainlp/tokenize/core.py Replaces type ignore with cast for paragraph tokenization return.
pythainlp/tag/wangchanberta_onnx.py Tightens ONNX IO types, improves SentencePiece loading compatibility, and adds casts.
pythainlp/tag/tltk.py Replaces type ignore with cast for tltk POS tagging results.
pythainlp/tag/thainer.py Narrows feature dict typing for CRF feature extraction.
pythainlp/tag/thai_nner.py Narrows entity container typing to dict[str, Any] across helpers.
pythainlp/tag/crfchunk.py Formatting-only (blank line) in deprecated shim.
pythainlp/tag/chunk.py Formatting-only (blank line) in deprecated shim.
pythainlp/tag/_tag_perceptron.py Narrows JSON payload typing to Any to satisfy type-checkers.
pythainlp/summarize/keybert.py Improves exception chaining and adds NDArray typing/casts for embeddings.
pythainlp/summarize/freq.py Refactors frequency calculation to Counter/dict and tightens typing.
pythainlp/spell/words_spelling_correction.py Improves dependency checking, adds typing/docstrings, and introduces a simple cache.
pythainlp/spell/tltk.py Replaces type ignore with cast for spell candidate output.
pythainlp/spell/phunspell.py Replaces type ignore with cast for correction return type.
pythainlp/soundex/sound.py Replaces type ignore with cast for panphon vector output.
pythainlp/soundex/complete_soundex.py Narrows helper parameter/return typings for soundex processing.
pythainlp/phayathaibert/core.py Tightens callable types and replaces type ignore with cast for tokenization.
pythainlp/parse/ud_goeswith.py Improves variable naming and formatting for root adjustment logic.
pythainlp/generate/wangchanglm.py Narrows regex pattern typing and replaces type ignore with cast for decoding output.
pythainlp/el/core.py Tightens EL return typing (list[dict[str, Any]]) and fixes docstring param line.
pythainlp/el/_multiel.py Tightens return typing and replaces type ignore with cast for external output.
pythainlp/corpus/wordnet.py Fixes Synset/Lemma typing imports and replaces ignores with casts in wrappers.
pythainlp/corpus/core.py Replaces ignores with casts, cleans typing imports, and ensures string return for HF hub path.
pythainlp/corpus/common.py Formatting-only restructuring for readability.
pythainlp/coref/core.py Replaces singleton model with keyed cache and tightens return typing/casts.
pythainlp/chunk/crfchunk.py Replaces type ignore with cast for tagger output typing.
pythainlp/chunk/init.py Formatting-only (blank line).
pythainlp/benchmarks/word_tokenization.py Adds NDArray typing, dtype fixes, and improves exception chaining.
pythainlp/augment/wordnet.py Narrows list typings for synsets and synonym lists.
pythainlp/augment/word2vec/core.py Formatting-only change to wrap long call for readability.
pythainlp/augment/word2vec/bpemb_wv.py Replaces type ignore with cast for BPEmb encode return.
pythainlp/ancient/currency.py Narrows return type to dict[str, float] and fixes a comment.
Comments suppressed due to low confidence (1)

pythainlp/wsd/core.py:17

  • _TRIE/_word_cut are built from _mean_all.keys() before _mean_all is populated (the loop that fills _mean_all happens later). This makes _TRIE empty at import time, so the default custom_tokenizer is likely initialized with an empty dictionary. Build _all_word/_TRIE/_word_cut after the for ... zip(words, meanings) loop (or build them lazily) so they reflect the actual WSD vocabulary.

Comment thread pythainlp/corpus/wordnet.py Outdated
'calculate'
"""
return wordnet.morphy(form, pos=None) # type: ignore[no-any-return]
return cast(str, wordnet.morphy(form, pos=None))

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

morphy() accepts a pos argument but currently ignores it by always calling wordnet.morphy(form, pos=None). This makes the pos parameter ineffective and can return incorrect base forms for callers that pass a part-of-speech. Pass through the provided pos value to wordnet.morphy.

Suggested change
return cast(str, wordnet.morphy(form, pos=None))
return cast(str, wordnet.morphy(form, pos=pos))

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add test cases for this too.
At least one test case for None and another for no n-None.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added two test cases in testx_corpus.py: one for morphy("dogs", pos=None) and one for morphy("dogs", pos="n"), both asserting the result is "dog". Fixed in fea0717.

Comment thread pythainlp/tag/tltk.py Outdated
@@ -20,7 +20,7 @@
def pos_tag(words: list[str], corpus: str = "tnc") -> list[tuple[str, str]]:
if corpus != "tnc":
raise ValueError(f"tltk not support {0} corpus.")

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ValueError message uses {0} instead of interpolating the provided corpus value, so it will always render as "tltk not support 0 corpus.". Use the corpus variable (ideally with !r) in the f-string so the error is actionable.

Suggested change
raise ValueError(f"tltk not support {0} corpus.")
raise ValueError(f"tltk not support {corpus!r} corpus.")

Copilot uses AI. Check for mistakes.
Comment thread pythainlp/util/numtoword.py Outdated
Comment on lines +33 to +41
def bahttext(number: Optional[float]) -> str:
"""Converts a number to Thai text and adds
a suffix "บาท" (Baht).
The precision will be fixed at two decimal places (0.00)
to fit "สตางค์" (Satang) unit.
This function works similarly to the `BAHTTEXT` function in Microsoft Excel.

:param float number: number to be converted into Thai Baht currency format
:param Optional[float] number: number to be converted into Thai Baht
currency format

Copilot AI Mar 22, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature/docstring now allow number: Optional[float], which implies None is a supported input. To avoid an ambiguous API contract, either keep number as float (and reject None), or explicitly document what bahttext(None) should return/raise and implement that behavior consistently.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should reject None.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed bahttext to number: float (removes Optional), added a TypeError guard at the top of the function, and updated the test in test_util.py from assertEqual(bahttext(None), "") to assertRaises(TypeError). Fixed in fea0717.

@bact

bact commented Mar 22, 2026

Copy link
Copy Markdown
Member

@copilot look at suggestions and comments in thread #1354 (review) and fix all the issues.

Run mypy, pyright, pylint, ruff, flake8 and fix all issues.

Self-review and add test cases that addressed issues fixed in this PR.

Copilot AI commented Mar 22, 2026

Copy link
Copy Markdown
Contributor Author

@copilot look at suggestions and comments in thread #1354 (review) and fix all the issues.

Run mypy, pyright, pylint, ruff, flake8 and f...

All three review issues are addressed in fea0717:

  • morphy() now passes pos through correctly (pos=pos instead of pos=None)
  • bahttext() rejects None with a TypeError (signature is float, not Optional[float])
  • tltk.py ValueError message fixed to {corpus!r} instead of {0}
  • Added test cases for morphy with both pos=None and non-None pos; updated bahttext(None) test to expect TypeError
  • mypy (199 files), ruff (all checks): no issues

Copilot AI changed the title Narrow type hints safely from PR #1344, fix runtime and mypy errors Fix runtime AttributeError in wordnet type casts, improve type safety and API contracts Mar 22, 2026
Copilot finished work on behalf of bact March 22, 2026 13:09
@sonarqubecloud

Copy link
Copy Markdown

@bact
bact merged commit 8a557df into dev Mar 22, 2026
27 of 28 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in PyThaiNLP Mar 22, 2026
@bact
bact deleted the copilot/narrow-type-hints-update branch March 22, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring a technical improvement which does not add any new features or change existing features.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants